﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Reflection;
using System.Reflection.Emit;
using System.Windows;

using Microsoft.SharePoint.Client;

using SBPweb.RIA.Client.Common.Collections;

namespace SBPweb.PROvibe.Client.Modules.MOSS14Connector.Model.Lists
{

    public class ListItemValuesFactory
    {

        private TypeBuilder tb;
        private Dictionary<string, DependencyProperty> fieldProperties = new Dictionary<string, DependencyProperty>();

        public ListItemValuesFactory(string typeName, IEnumerable<Field> fields)
        {
            tb = CreateTypeBuilder(typeName, fields);
            foreach (Field field in fields)
            {
                fieldProperties.Add(field.StaticName, DependencyProperty.Register(field.StaticName, typeof(object), typeof(ListItemValues), null));
            }
        }


        public ListItemValues CreateItemInstance(ListItem item)
        {
            ListItemValues ret = Activator.CreateInstance(tb.CreateType()) as ListItemValues;

            ret.SetListItem(fieldProperties, item);

            return ret;
        }


        public IList CreateListInstance()
        {
            Type t = typeof(ObservableList<ListItemValues>).GetGenericTypeDefinition();
            t = t.MakeGenericType(tb.CreateType());
            
            return Activator.CreateInstance(t) as IList;
        }

        private static TypeBuilder CreateTypeBuilder(string typeNamePostFix, IEnumerable<Field> fields)
        {
            Type type = typeof(ListItemValues);
            AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("DynamicAssembly"), AssemblyBuilderAccess.Run);
            ModuleBuilder mb = ab.DefineDynamicModule("DynamicModule");
            TypeBuilder tb = mb.DefineType(string.Format("{0}_{1}", type.Name, typeNamePostFix),
                TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.AutoClass | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit | TypeAttributes.AutoLayout,
                type);

            ConstructorBuilder constructor = tb.DefineConstructor(
                MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
                CallingConventions.Standard,
                Type.EmptyTypes);
            ConstructorInfo conObj = typeof(ListItemValues).GetConstructor(new Type[0]);

            ILGenerator il = constructor.GetILGenerator();
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Call, conObj);
            il.Emit(OpCodes.Ret);

            foreach (Field f in fields)
            {
                MethodInfo miG = type.GetMethod("GetValue", BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(string) }, new ParameterModifier[] { new ParameterModifier(1) });
                MethodInfo miS = type.GetMethod("SetValue", BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(string), typeof(object) }, new ParameterModifier[] { new ParameterModifier(2) });

                BuildPropertyAccessors(tb, miG, miS, f.StaticName);
            }

            return tb;
        }

        private static void BuildPropertyAccessors(TypeBuilder tb, MethodInfo miG, MethodInfo miS, string name)
        {
            PropertyBuilder pb = tb.DefineProperty(name, PropertyAttributes.HasDefault, typeof(object), Type.EmptyTypes);

            MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;

            MethodBuilder mbPropGetAccessor = tb.DefineMethod(
                "get_" + name,
                getSetAttr,
                typeof(object),
                Type.EmptyTypes);

            // Define the "set" accessor method for Number, which has no return
            // type and takes one argument of type int (Int32).
            MethodBuilder mbPropSetAccessor = tb.DefineMethod(
                "set_" + name,
                getSetAttr,
                null,
                new Type[] { typeof(object) });

            ILGenerator propGetIL = mbPropGetAccessor.GetILGenerator();
            propGetIL.Emit(OpCodes.Nop);
            propGetIL.Emit(OpCodes.Ldarg_0);
            propGetIL.Emit(OpCodes.Ldstr, name);
            propGetIL.Emit(OpCodes.Call, miG);
            propGetIL.Emit(OpCodes.Nop);
            propGetIL.Emit(OpCodes.Ret);

            ILGenerator propSetIL = mbPropSetAccessor.GetILGenerator();
            propSetIL.Emit(OpCodes.Nop);
            propSetIL.Emit(OpCodes.Ldarg_0);
            propSetIL.Emit(OpCodes.Ldstr, name);
            propSetIL.Emit(OpCodes.Ldarg_1);
            propSetIL.Emit(OpCodes.Call, miS);
            propSetIL.Emit(OpCodes.Nop);
            propSetIL.Emit(OpCodes.Ret);

            pb.SetGetMethod(mbPropGetAccessor);
            pb.SetSetMethod(mbPropSetAccessor);
        }

    }

}
